/* Copyright 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.code.samples.xoauth;

import com.sun.mail.imap.IMAPSSLStore;
import com.sun.mail.imap.IMAPMessage;
import com.sun.mail.smtp.SMTPTransport;
import com.sun.mail.util.BASE64EncoderStream;

import net.oauth.OAuthConsumer;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.Provider;
import java.security.Security;
import java.util.Date;
import java.util.Hashtable;
import java.util.Properties;

import javax.mail.Address;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.URLName;

import javax.mail.internet.*;


/**
 * Performs XOAUTH authentication.
 *
 * <p>Before using this class, you must call {@code initialize} to install the
 * XOAUTH SASL provider.
 */
public class XoauthAuthenticator {
	
	static boolean showStructure = false;
	static boolean saveAttachments = true;
    static int attnum = 1;
	
  public static final class XoauthProvider extends Provider {
    public XoauthProvider() {
      super("Google Xoauth Provider", 1.0,
            "Provides the Xoauth experimental SASL Mechanism");
      put("SaslClientFactory.XOAUTH",
          "com.google.code.samples.xoauth.XoauthSaslClientFactory");
    }
  }

  /**
   * Generates a new OAuthConsumer with token and secret of
   * "anonymous"/"anonymous". This can be used for testing.
   */
  public static OAuthConsumer getAnonymousConsumer() {
    return new OAuthConsumer(null, "anonymous", "anonymous", null);
  }

  /**
   * Installs the XOAUTH SASL provider. This must be called exactly once before
   * calling other methods on this class.
   */
  public static void initialize() {
    Security.addProvider(new XoauthProvider());
  }

  /**
   * Connects and authenticates to an IMAP server with XOAUTH. You must have
   * called {@code initialize}.
   *
   * @param host Hostname of the imap server, for example {@code
   *     imap.googlemail.com}.
   * @param port Port of the imap server, for example 993.
   * @param userEmail Email address of the user to authenticate, for example
   *     {@code xoauth@gmail.com}.
   * @param oauthToken The user's OAuth token.
   * @param oauthTokenSecret The user's OAuth token secret.
   * @param consumer The application's OAuthConsumer. For testing, use
   *     {@code getAnonymousConsumer()}.
   * @param debug Whether to enable debug logging on the IMAP connection.
   *
   * @return An authenticated IMAPSSLStore that can be used for IMAP operations.
   */
  public static IMAPSSLStore connectToImap(String host,
                                           int port,
                                           String userEmail,
                                           String oauthToken,
                                           String oauthTokenSecret,
                                           OAuthConsumer consumer,
                                           boolean debug) throws Exception {
    Properties props = new Properties();
    props.put("mail.imaps.fetchsize", "8388608");  // ÷�� ���� �ӵ� ����
    props.put("mail.imaps.sasl.enable", "true");
    props.put("mail.imaps.sasl.mechanisms", "XOAUTH");
    props.put(XoauthSaslClientFactory.OAUTH_TOKEN_PROP,
              oauthToken);
    props.put(XoauthSaslClientFactory.OAUTH_TOKEN_SECRET_PROP,
              oauthTokenSecret);
    props.put(XoauthSaslClientFactory.CONSUMER_KEY_PROP,
              consumer.consumerKey);
    props.put(XoauthSaslClientFactory.CONSUMER_SECRET_PROP,
              consumer.consumerSecret);
    Session session = Session.getInstance(props);
    session.setDebug(debug);

    final URLName unusedUrlName = null;
    IMAPSSLStore store = new IMAPSSLStore(session, unusedUrlName);
    final String emptyPassword = "";
    store.connect(host, port, userEmail, emptyPassword);
    return store;
  }

  /**
   * Connects and authenticates to an SMTP server with XOAUTH. You must have
   * called {@code initialize}.
   *
   * @param host Hostname of the smtp server, for example {@code
   *     smtp.googlemail.com}.
   * @param port Port of the smtp server, for example 587.
   * @param userEmail Email address of the user to authenticate, for example
   *     {@code xoauth@gmail.com}.
   * @param oauthToken The user's OAuth token.
   * @param oauthTokenSecret The user's OAuth token secret.
   * @param consumer The application's OAuthConsumer. For testing, use
   *     {@code getAnonymousConsumer()}.
   * @param debug Whether to enable debug logging on the connection.
   *
   * @return An authenticated SMTPTransport that can be used for SMTP
   *     operations.
   */
  public static SMTPTransport connectToSmtp(String host,
                                            int port,
                                            String userEmail,
                                            String oauthToken,
                                            String oauthTokenSecret,
                                            OAuthConsumer consumer,
                                            boolean debug) throws Exception {
    Properties props = new Properties();
    props.put("mail.smtp.ehlo", "true");
    props.put("mail.smtp.auth", "false");
    props.put("mail.smtp.starttls.enable", "true");
    props.put("mail.smtp.starttls.required", "true");
    props.put("mail.smtp.sasl.enable", "false");
    Session session = Session.getInstance(props);
    session.setDebug(debug);

    final URLName unusedUrlName = null;
    SMTPTransport transport = new SMTPTransport(session, unusedUrlName);
    // If the password is non-null, SMTP tries to do AUTH LOGIN.
    final String emptyPassword = null;
    transport.connect(host, port, userEmail, emptyPassword);

    /*
     * I couldn't get the SASL infrastructure to work with JavaMail 1.4.3;
     * I don't think it was ready yet in that release. So we'll construct the
     * AUTH command manually.
     */
    XoauthSaslResponseBuilder builder = new XoauthSaslResponseBuilder();
    byte[] saslResponse = builder.buildResponse(userEmail,
                                                XoauthProtocol.SMTP,
                                                oauthToken,
                                                oauthTokenSecret,
                                                consumer);
    saslResponse = BASE64EncoderStream.encode(saslResponse);
    transport.issueCommand("AUTH XOAUTH " + new String(saslResponse),
                           235);
    return transport;
  }

  /**
   * Authenticates to IMAP with parameters passed in on the commandline.
   */
  //public static void main(String args[]) throws Exception {
  public String getMessage(String _uploadpath, String _email, String _oauth_token, String _oauth_token_secret, String _hash_code) throws Exception {
//	String  to, subject = null, from = null, cc = null, bcc = null, url = null;
	
//	to = "tapaha7@gmail.com";
//	from = "tapaha@ajou.ac.kr";
	  
    String email = _email;
    String oauthToken = _oauth_token;
    String oauthTokenSecret = _oauth_token_secret;
    String hashCode = _hash_code;
    String retPath = "";

    initialize();

    IMAPSSLStore imapSslStore = connectToImap("imap.googlemail.com",
                                              993,
                                              email,
                                              oauthToken,
                                              oauthTokenSecret,
                                              getAnonymousConsumer(),
                                              true);
    System.out.println("Successfully authenticated to IMAP.\n");
    
    Folder folder = imapSslStore.getDefaultFolder();
    
	if (folder == null) {
		System.out.println("No default folder");
		System.exit(1);
	}
	
	folder = folder.getFolder("adonate");
	
	// try to open read/write and if that fails try read-only
    try {
    	folder.open(Folder.READ_WRITE);
    } catch (MessagingException ex) {
    	folder.open(Folder.READ_ONLY);
    }
    
    int totalMessages = folder.getMessageCount();

    if (totalMessages == 0) {
		System.out.println("Empty folder");
		folder.close(false);
		imapSslStore.close();
		System.exit(1);
    }
    
    System.out.println("totalMessages : " + totalMessages);
    
    Message[] msgs = folder.getMessages();
    
    Part p = null;
    
    for(int idx=0;idx<msgs.length;idx++) {
    	//Parsing후 HashCode 체크
//    	System.out.println("msgs[idx].getContentType() : " + msgs[idx].getContentType());
//    	System.out.println("msgs[idx].getMessageNumber() : " + msgs[idx].getMessageNumber());
//    	System.out.println("msgs[idx].getSubject() : " + msgs[idx].getSubject());
//    	System.out.println("msgs[idx].getFileName() : " + msgs[idx].getFileName());
    	
    	if(isMatchedMessage(msgs[idx].getSubject(), hashCode)) {
		    try {
		    	retPath = _uploadpath + getFileName(msgs[idx].getSubject(), hashCode);
		    	System.out.println("retPath : " + retPath);
		    	dumpPart(msgs[idx], _uploadpath);
		    } catch (IndexOutOfBoundsException iex) {
		    	System.out.println("Message number out of range");
		    }
    	}
    }
    
    folder.close(false);
    imapSslStore.close();
    
    return retPath;
  }
  
  public boolean isMatchedMessage(String subject, String hash) {
	  String []info  = subject.split("]");
	  
	  System.out.println("XOAuth - isMatchedMessage()");
	  System.out.println("Subject : " + subject);
	  System.out.println("Hashcode : " + hash);
	  
	  //hash code
	  int flag = info[2].indexOf(":");
	  info[2] = info[2].substring(flag+1);
	  
	  if(info[2].equals(hash)) {
		  return true;
	  } else {
		  return false;
	  }
  }
  
  public String getFileName(String subject, String hash) {
	  String []info  = subject.split("]");
	  int flag = 0;
	  
	  System.out.println("XOAuth - isMatchedMessage()");
	  System.out.println("Subject : " + subject);
	  System.out.println("Hashcode : " + hash);
	  
	  //hash code
	  flag = info[1].indexOf(":");
	  info[1] = info[1].substring(flag+1);
	  
	  flag = info[2].indexOf(":");
	  info[2] = info[2].substring(flag+1);
	  
	  if(info[2].equals(hash)) {
		  return info[1];
	  } else {
		  return "";
	  }
  }
  
  public static void dumpPart(Part p, String _uploadpath) throws Exception {
	  String mUploadPath = _uploadpath;
	  
		if (p instanceof Message)
		    dumpEnvelope((Message)p);

		/** Dump input stream .. 

		InputStream is = p.getInputStream();
		// If "is" is not already buffered, wrap a BufferedInputStream
		// around it.
		if (!(is instanceof BufferedInputStream))
		    is = new BufferedInputStream(is);
		int c;
		while ((c = is.read()) != -1)
		    System.out.write(c);

		**/

		String ct = p.getContentType();
		try {
		    pr("CONTENT-TYPE: " + (new ContentType(ct)).toString());
		} catch (ParseException pex) {
		    pr("BAD CONTENT-TYPE: " + ct);
		}
		String filename = p.getFileName();
		if (filename != null)
		    pr("FILENAME: " + filename);

		/*
		 * Using isMimeType to determine the content type avoids
		 * fetching the actual content data until we need it.
		 */
		if (p.isMimeType("text/plain")) {
		    pr("This is plain text");
		    pr("---------------------------");
		    if (!showStructure && !saveAttachments)
			System.out.println((String)p.getContent());
		} else if (p.isMimeType("multipart/*")) {
		    pr("This is a Multipart");
		    pr("---------------------------");
		    Multipart mp = (Multipart)p.getContent();
		    level++;
		    int count = mp.getCount();
		    for (int i = 0; i < count; i++)
			dumpPart(mp.getBodyPart(i), mUploadPath);
		    level--;
		} else if (p.isMimeType("message/rfc822")) {
		    pr("This is a Nested Message");
		    pr("---------------------------");
		    level++;
		    dumpPart((Part)p.getContent(), mUploadPath);
		    level--;
		} else {
		    if (!showStructure && !saveAttachments) {
			/*
			 * If we actually want to see the data, and it's not a
			 * MIME type we know, fetch it and check its Java type.
			 */
			Object o = p.getContent();
			if (o instanceof String) {
			    pr("This is a string");
			    pr("---------------------------");
			    System.out.println((String)o);
			} else if (o instanceof InputStream) {
			    pr("This is just an input stream");
			    pr("---------------------------");
			    InputStream is = (InputStream)o;
			    int c;
			    while ((c = is.read()) != -1)
				System.out.write(c);
			} else {
			    pr("This is an unknown type");
			    pr("---------------------------");
			    pr(o.toString());
			}
		    } else {
			// just a separator
			pr("---------------------------");
		    }
		}

		/*
		 * If we're saving attachments, write out anything that
		 * looks like an attachment into an appropriately named
		 * file.  Don't overwrite existing files to prevent
		 * mistakes.
		 */
		if (saveAttachments && level != 0 && !p.isMimeType("multipart/*")) {
		    String disp = p.getDisposition();
		    // many mailers don't include a Content-Disposition
		    if (disp == null || disp.equalsIgnoreCase(Part.ATTACHMENT)) {
			if (filename == null)
			    filename = "Attachment" + attnum++;
			pr("Saving attachment to file " + filename);
			try {
			    File f = new File(mUploadPath + filename);
			    if (f.exists())
				// XXX - could try a series of names
				throw new IOException("file exists");
			    ((MimeBodyPart)p).saveFile(f);
			} catch (IOException ex) {
			    pr("Failed to save attachment: " + ex);
			}
			pr("---------------------------");
		    }
		}
	    }
  public static void dumpEnvelope(Message m) throws Exception {
		pr("This is the message envelope");
		pr("---------------------------");
		Address[] a;
		// FROM 
		if ((a = m.getFrom()) != null) {
		    for (int j = 0; j < a.length; j++)
			pr("FROM: " + a[j].toString());
		}

		// REPLY TO
		if ((a = m.getReplyTo()) != null) {
		    for (int j = 0; j < a.length; j++)
			pr("REPLY TO: " + a[j].toString());
		}

		// TO
		if ((a = m.getRecipients(Message.RecipientType.TO)) != null) {
		    for (int j = 0; j < a.length; j++) {
			pr("TO: " + a[j].toString());
			InternetAddress ia = (InternetAddress)a[j];
			if (ia.isGroup()) {
			    InternetAddress[] aa = ia.getGroup(false);
			    for (int k = 0; k < aa.length; k++)
				pr("  GROUP: " + aa[k].toString());
			}
		    }
		}

		// SUBJECT
		pr("SUBJECT: " + m.getSubject());

		// DATE
		Date d = m.getSentDate();
		pr("SendDate: " +
		    (d != null ? d.toString() : "UNKNOWN"));

		// FLAGS
		Flags flags = m.getFlags();
		StringBuffer sb = new StringBuffer();
		Flags.Flag[] sf = flags.getSystemFlags(); // get the system flags

		boolean first = true;
		for (int i = 0; i < sf.length; i++) {
		    String s;
		    Flags.Flag f = sf[i];
		    if (f == Flags.Flag.ANSWERED)
			s = "\\Answered";
		    else if (f == Flags.Flag.DELETED)
			s = "\\Deleted";
		    else if (f == Flags.Flag.DRAFT)
			s = "\\Draft";
		    else if (f == Flags.Flag.FLAGGED)
			s = "\\Flagged";
		    else if (f == Flags.Flag.RECENT)
			s = "\\Recent";
		    else if (f == Flags.Flag.SEEN)
			s = "\\Seen";
		    else
			continue;	// skip it
		    if (first)
			first = false;
		    else
			sb.append(' ');
		    sb.append(s);
		}

		String[] uf = flags.getUserFlags(); // get the user flag strings
		for (int i = 0; i < uf.length; i++) {
		    if (first)
			first = false;
		    else
			sb.append(' ');
		    sb.append(uf[i]);
		}
		pr("FLAGS: " + sb.toString());

		// X-MAILER
		String[] hdrs = m.getHeader("X-Mailer");
		if (hdrs != null)
		    pr("X-Mailer: " + hdrs[0]);
		else
		    pr("X-Mailer NOT available");
	    }

	    static String indentStr = "                                               ";
	    static int level = 0;

	    /**
	     * Print a, possibly indented, string.
	     */
	    public static void pr(String s) {
	    	if (showStructure) System.out.print(indentStr.substring(0, level * 2));
	    	System.out.println(s);
	    }
}
